package com.hjy.test;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//粘包现象
/*
可见虽然客户端是分别以16字节为单位，
通过channel向服务器发送了10次数据，可是服务器端却只接收了一次，
接收数据的大小为160B，即客户端发送的数据总大小，这就是粘包现象

半包现象
将客户端-服务器之间的channel容量进行调整

解决方案
1.短连接，发送完之后断开
2.发送定长数据 FixedLengthFrameDecoder
ch.pipeline().addLast(new FixedLengthFrameDecoder(16));
3.行解码器
行解码器的是通过分隔符对数据进行拆分来解决粘包半包问题的

可以通过LineBasedFrameDecoder(int maxLength)来拆分以换行符(\n)为分隔符的数据，也可以通过DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters)来指定通过什么分隔符来拆分数据（可以传入多个分隔符）

两种解码器都需要传入数据的最大长度，若超出最大长度，会抛出TooLongFrameException异常

以换行符 \n 为分隔符

4.长度字段解码器
在传送数据时可以在数据中添加一个用于表示有用数据长度的字段，在解码时读取出这个用于表明长度的字段，同时读取其他相关参数，即可知道最终需要的数据是什么样子的

LengthFieldBasedFrameDecoder解码器可以提供更为丰富的拆分方法，其构造方法有五个参数

public LengthFieldBasedFrameDecoder(
    int maxFrameLength,
    int lengthFieldOffset, int lengthFieldLength,
    int lengthAdjustment, int initialBytesToStrip)
参数解析

maxFrameLength 数据最大长度

表示数据的最大长度（包括附加信息、长度标识等内容）
lengthFieldOffset 数据长度标识的起始偏移量

用于指明数据第几个字节开始是用于标识有用字节长度的，因为前面可能还有其他附加信息
lengthFieldLength 数据长度标识所占字节数（用于指明有用数据的长度）

数据中用于表示有用数据长度的标识所占的字节数
lengthAdjustment 长度表示与有用数据的偏移量

用于指明数据长度标识和有用数据之间的距离，因为两者之间还可能有附加信息
initialBytesToStrip 数据读取起点

读取起点，不读取 0 ~ initialBytesToStrip 之间的数据
* */
public class StudyClient {
    static final Logger log = LoggerFactory.getLogger(StudyClient.class);

    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();
        try{
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(worker);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel channel) throws Exception {
                    log.debug("connected....");
                    channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            log.debug("sending...");
                            //每次发送16个字节的数据发送十次
                            for (int i = 0; i < 10; i++) {
                                ByteBuf byteBuf = ctx.alloc().directBuffer();
                                byteBuf.writeBytes(new byte[]{0,1,2,3,4,5,6,7,8,9,10});
                                ctx.writeAndFlush(byteBuf);
                            }
                        }
                    });
                }
            });
            ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
            future.channel().closeFuture().sync();
        }catch (Exception e){
            log.error("client error{}", e);
        } finally {
            worker.shutdownGracefully();
        }
    }
}
